﻿using Cassandra;
using Cassandra.Data.Linq;
using Cognitum.CassandraTableStorage.Entities;
using Cognitum.CassandraTableStorage.Extensions;
using System;
using System.Collections.Generic;
using System.Data.Services.Client;
using System.Linq;
using System.Text;

namespace Cognitum.CassandraTableStorage
{
    public class CassandraTableStorageServiceContext
    {
        #region Fields

        //Number of users names to matched in find user method (on CassandraTableStorageDataSource).
        private const int _PAGE_SIZE_ON_GET_USER_NAMES = 50;

        private static readonly object _clusterLock = new object();
        static private Cluster _cluster;
        private ISession _session;
        private int _replicationFactor;

        #endregion Fields

        #region Constructors

        public CassandraTableStorageServiceContext(string baseAddress, int replicationFactor)
        {
            _replicationFactor = replicationFactor;
            ConnectToCluster(baseAddress);
            MakeSession();
            MakeTables();
        }

        #endregion Constructors

        #region Properties

        public Cluster Cluster { get { return _cluster; } }

        public Table<role_entity> Roles
        {
            get
            {
                Table<role_entity> table = SessionExtensions.GetTable<role_entity>(_session);
                return table;
            }
        }

        public ISession Session { get { return _session; } }

        public Table<session_state_entity> SessionStates
        {
            get
            {
                Table<session_state_entity> table = SessionExtensions.GetTable<session_state_entity>(_session);
                return table;
            }
        }

        public Table<user_entity> Users
        {
            get
            {
                Table<user_entity> table = SessionExtensions.GetTable<user_entity>(_session);
                return table;
            }
        }

        public Table<role_user_entity> UsersInRole
        {
            get
            {
                Table<role_user_entity> table = SessionExtensions.GetTable<role_user_entity>(_session);
                return table;
            }
        }

        public Table<user_active_entity> UsersActive
        {
            get
            {
                Table<user_active_entity> table = SessionExtensions.GetTable<user_active_entity>(_session);
                return table;
            }
        }

        public Table<user_online_entity> UsersOnLine
        {
            get
            {
                Table<user_online_entity> table = SessionExtensions.GetTable<user_online_entity>(_session);
                return table;
            }
        }

        #endregion Properties

        #region Methods

        public int GetNumberOfActiveProfiles(string applicationName)
        {
            user_active_entity useractiveentity = new user_active_entity();
            return (int)_session.Execute(
                 new SimpleStatement("SELECT count(*) FROM " +
                    typeof(user_active_entity).Name + " WHERE \"" +
                    useractiveentity.PropertyName(a => a.application_name) + "\" = '" +
                   applicationName + "' ALLOW FILTERING;")
                ).GetRows().First().GetValue<Int64>("count");
        }

        public int GetNumberAllProfiles(string applicationName)
        {
            user_entity userentity = new user_entity();
            return (int)_session.Execute(
                 new SimpleStatement("SELECT count(*) FROM " +
                    typeof(user_entity).Name + " WHERE \"" +
                    userentity.PropertyName(a => a.application_name) + "\" = '" +
                   applicationName + "' AND \"is_profile_deleted\" = " + false + " ALLOW FILTERING;")
                ).GetRows().First().GetValue<Int64>("count");
        }

        public IEnumerable<Row> GetUsernamesFromActiveUsers(string applicationName)
        {
            user_active_entity useractiveentity = new user_active_entity();
            foreach (Row row in _session.Execute(
                new SimpleStatement("SELECT username FROM " +
                     typeof(user_active_entity).Name + " WHERE  \"" +
                     useractiveentity.PropertyName(a => a.application_name) + "\" = '" +
                    applicationName +
                    "' ALLOW FILTERING;")
                    .SetPageSize(_PAGE_SIZE_ON_GET_USER_NAMES))
                    .GetRows())
                yield return row;
        }

        public IEnumerable<Row> GetUsernamesFromRoles(string applicationName, string roleName)
        {
            role_user_entity roleuserentity = new role_user_entity();
            foreach (Row row in _session.Execute(
                new SimpleStatement("SELECT user_name FROM " +
                    typeof(role_user_entity).Name +
                    " WHERE \"" +
                    roleuserentity.PropertyName(a => a.role_name) + "\" = '" +
                    roleName + "' AND " +
                    "\"" +
                    roleuserentity.PropertyName(a => a.application_name) + "\" = '" +
                    applicationName + "';").
                    SetPageSize(_PAGE_SIZE_ON_GET_USER_NAMES)).
                    GetRows())
                yield return row;
        }

        public IEnumerable<Row> GetUsernamesFromUsers(string applicationName)
        {
            user_entity userentity = new user_entity();
            foreach (Row row in _session.Execute(
                new SimpleStatement("SELECT username FROM " +
                     typeof(user_entity).Name + " WHERE \"" +
                     userentity.PropertyName(a => a.application_name) + "\" = '" +
                    applicationName + "' ALLOW FILTERING;").
                    SetPageSize(_PAGE_SIZE_ON_GET_USER_NAMES)).
                    GetRows())
                yield return row;
        }

        public IEnumerable<Row> GetUsernamesFromUsersProfiles(string applicationName)
        {
            user_entity userentity = new user_entity();
            foreach (Row row in _session.Execute(
                new SimpleStatement("SELECT username FROM " +
                     typeof(user_entity).Name + " WHERE \"" +
                     userentity.PropertyName(a => a.application_name) + "\" = '" +
                    applicationName +
                    "' AND \"" +
                    userentity.PropertyName(a => a.is_profile_deleted) + "\" = " + false +
                    " ALLOW FILTERING;").
                    SetPageSize(_PAGE_SIZE_ON_GET_USER_NAMES)).
                    GetRows())
                yield return row;
        }

        public IEnumerable<Row> GetEmailsFromUsers(string applicationName)
        {
            user_entity userentity = new user_entity();
            foreach (Row row in _session.Execute(
               new SimpleStatement("SELECT username,email FROM " +
                    typeof(user_entity).Name + " WHERE \"" +
                    userentity.PropertyName(a => a.application_name) + "\" = '" +
                   applicationName + "' ALLOW FILTERING;").
                   SetPageSize(_PAGE_SIZE_ON_GET_USER_NAMES)).
                   GetRows())
                yield return row;
        }

        public int GetNumberOfUsersOnline(string applicationName)
        {
            user_entity userentity = new user_entity();
            return (int)_session.Execute(
                 new SimpleStatement("SELECT count(*) FROM " +
                    typeof(user_online_entity).Name + " WHERE \"" +
                    userentity.PropertyName(a => a.application_name) + "\" = '" +
                   applicationName + "' ALLOW FILTERING;")
                ).GetRows().First().GetValue<Int64>("count");
        }

        /// <summary>
        /// Returns a reference to the Cassandra cluster used to persist session state.
        /// </summary>
        /// <returns></returns>
        private void ConnectToCluster(string nodeAdress)
        {
            if (_cluster == null)
            {
                lock (_clusterLock)
                {
                    if (_cluster == null)
                    {
                        _cluster = Cluster.Builder().AddContactPoint(nodeAdress)
                         .WithRetryPolicy(DowngradingConsistencyRetryPolicy.Instance)
                         .WithReconnectionPolicy(new ConstantReconnectionPolicy(100L))
                         .WithDefaultKeyspace("cassandraprovider").Build();
                    }
                }
            }
        }

        private void MakeSession()
        {
            _session = null;
            Dictionary<string, string> replication = new Dictionary<string, string>
            {
                { "class", ReplicationStrategies.SimpleStrategy },
                { "replication_factor", _replicationFactor.ToString() }
            };
            _session = Cluster.ConnectAndCreateDefaultKeyspaceIfNotExists(replication, true);
        }

        private void MakeTables()
        {
            Table<user_entity> tableUserEntity = SessionExtensions.GetTable<user_entity>(_session);
            tableUserEntity.CreateIfNotExists();
            Table<role_entity> tableRoleEntity = SessionExtensions.GetTable<role_entity>(_session);
            tableRoleEntity.CreateIfNotExists();
            Table<session_state_entity> tableSessionStateEntity = SessionExtensions.GetTable<session_state_entity>(_session);
            tableSessionStateEntity.CreateIfNotExists();
            Table<user_online_entity> tableUserOnLineEntity = SessionExtensions.GetTable<user_online_entity>(_session);
            tableUserOnLineEntity.CreateIfNotExists();
            Table<user_active_entity> tableActiveEntity = SessionExtensions.GetTable<user_active_entity>(_session);
            tableActiveEntity.CreateIfNotExists();
            Table<role_user_entity> tableRoleUserEntity = SessionExtensions.GetTable<role_user_entity>(_session);
            tableRoleUserEntity.CreateIfNotExists();
        }

        #endregion Methods
    }
}